/*
    This file is part of QTau
    Copyright (C) 2013-2019  Tobias "Tomoko" Platen <tplaten@posteo.de>
    Copyright (C) 2013       digited       <https://github.com/digited>
    Copyright (C) 2010-2013  HAL@ShurabaP  <https://github.com/haruneko>

    QTau is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

    SPDX-License-Identifier: GPL-3.0+
*/

// a simple demo synth using the EpR voice model
// using freqs from https://en.wikipedia.org/wiki/Formant

#include "epr_synth.h"
#include <sekai/midi.h>


#define __devloglevel__ 4

QString EpRSynth::name()        { return "EpR"; }
QString EpRSynth::description() { return "Based on the Klatt synthesizer"; }
QString EpRSynth::version()     { return "19.04"; }
bool EpRSynth::synthIsRealtime() { return true; }

void __devlog__(QString logtype,QString filename,int line,QString msg)
{
    QString logmsg = logtype+": "+filename.replace("../../","").replace("../","")+":"+QVariant(line).toString()+": "+msg;
    fprintf(stderr,"[synth]%s\n",logmsg.toUtf8().data());
}

void EpRSynth::setup(IController* ctrl)      {
    this->_ctrl = ctrl;
    this->_jack_samplerate = ctrl->sampleRate();

    connect(this,&EpRSynth::logDebug,this,&EpRSynth::on_logDebug);
    connect(this,&EpRSynth::logError,this,&EpRSynth::on_logError);
    connect(this,&EpRSynth::logSuccess,this,&EpRSynth::on_logSuccess);
    connect(this,&EpRSynth::endOfThread,this,&EpRSynth::on_endOfThread);

    fprintf(stderr,"ringbuffer allocate %i\n",_jack_samplerate);


    _src.gaindb = 40;
    _src.slope = -0.00001;
    _src.slopedepthdb = 77;
    _osc.fs=_jack_samplerate;

    formant fo;
    fo.f1 = 860;
    fo.f2 = 1610;
    _formantMap["a"]=fo;
    fo.f1 = 360;
    fo.f2 = 640;
    _formantMap["o"]=fo;
    fo.f1 = 390;
    fo.f2 = 2300;
    _formantMap["e"]=fo;
    fo.f1 = 240;
    fo.f2 = 2400;
    _formantMap["i"]=fo;
    fo.f1 = 250;
    fo.f2 = 595;
    _formantMap["u"]=fo;

}



bool EpRSynth::synthesize(IScore* score)
{
    _pos = 0;
    _currentNote = 0;
    _stop = false;
    _notes.clear();
    for(int i=0;i<score->getNoteCount();i++)
    {
        auto n = score->getNote(i);
        DEVLOG_DEBUG("note "+STR(i));
        DEVLOG_DEBUG("rest "+STR(n.rest)+" length "+STR(n.lenght));
        noteEvent evt;
        evt.startPos = n.start;
        evt.endPos = n.start+n.lenght;
        evt.f0 = frequencyFromNote(n.pitch);
        evt.fo = _formantMap[n.lyric];
        _notes.push_back(evt);
    }
    return true;
}

void EpRSynth::genOneFrame()
{
    float f0 = 0;
    formant fo;

    if(_stop) return;

    //update EpR on specific timestamps
    float current_time = _pos*1.0/_jack_samplerate;

    if(current_time>_notes[_currentNote].startPos && current_time<_notes[_currentNote].endPos)
    {
        f0 = _notes[_currentNote].f0;
        fo = _notes[_currentNote].fo;
    }

    int nharmonics = 50;

    if(f0){


        _res[0].gain_db = 20;
        _res[0].bw = fo.f1/10.0;
        _res[0].f = fo.f1;
        _res[0].enabled = 1;

        _res[1].gain_db = 20;
        _res[1].bw = fo.f2/10.0;
        _res[1].f = fo.f2;
        _res[1].enabled = 1;

        for(int i=2;i<RES_COUNT;i++)
        {
            _res[i].gain_db = 5;
            _res[i].bw = 10;
            _res[i].f = i*1500;
            _res[i].enabled = 1;
        }


        for(int i=0;i<6;i++)
        {
            EprResonanceUpdate(&_res[i],_osc.fs);
        }

        for(int i=0;i<nharmonics;i++)
        {

                float factor = 0.1;
                float f = f0*(i+1);
                double gain = EprAtFrequency(&_src,f,_osc.fs,_res,RES_COUNT);
                _osc.amp[i] = pow(M_E,(gain/TWENTY_OVER_LOG10))/nharmonics*factor;
                _osc.frq[i] = f;
        }
    }
    else {
        for(int i=0;i<nharmonics;i++)
        {
             _osc.amp[i]=0;
             _osc.frq[i]=0;
        }
    }

    if(current_time>=_notes[_currentNote].endPos)
    {
       _currentNote++;
       if(_currentNote==_notes.count()) _stop=true;
    }
}


int EpRSynth::readData(float *data, int size)
{
    static jack_ringbuffer_t* my_ringbuffer=nullptr;

    int padding = 256;

    if(my_ringbuffer==nullptr)
    {
        my_ringbuffer = jack_ringbuffer_create(4096*16*sizeof(float));
    }

    while(jack_ringbuffer_read_space(my_ringbuffer)<(size+padding)*sizeof(float) && _stop==false )
    {

    genOneFrame();
    _pos += FFT_SIZE/2;

    _osc.processOneFrame();

    float output_buffer[FFT_SIZE/2];
    for(int i=0;i<FFT_SIZE/2;i++)
    {
        output_buffer[i] = _osc.output_buffer[i]*0.9;
    }

    if(jack_ringbuffer_write_space(my_ringbuffer)>FFT_SIZE/2*sizeof(float))
        jack_ringbuffer_write(my_ringbuffer,(char*)output_buffer,FFT_SIZE/2*sizeof(float));
    else
        fprintf(stderr,"buffer full\n");
    }



    if(jack_ringbuffer_read_space(my_ringbuffer)>(size+padding)*sizeof(float))
    {
        jack_ringbuffer_read(my_ringbuffer,(char*)data,size*sizeof(float));
    }
    else
    {
        if(_stop) return 1;
    }



    return 0;
}

QString EpRSynth::getTranscription(QString txt)
{
    return txt;
}

bool EpRSynth::doPhonemeTransformation(QStringList& list)
{
    (void) list;
    return false;
}

bool EpRSynth::setVoice(QString voiceName)
{
   if(voiceName=="Klatt") return true;
   return false;
}

QStringList EpRSynth::listVoices()
{
   QStringList voices;
   voices << "Klatt";
   return voices;
}

//??
void EpRSynth::on_logError(QString error)
{
    _ctrl->logError(error);
}

void EpRSynth::on_logSuccess(QString success)
{
    _ctrl->logSuccess(success);
}

void EpRSynth::on_logDebug(QString debug)
{
    _ctrl->logDebug(debug);
}

void EpRSynth::on_endOfThread()
{
}

bool EpRSynth::setCacheDir(QString cacheDir)
{
    (void) cacheDir;
    return true;
}
